'use strict';

const Redis = require('redis');

const keys = [
    'on',
    'set',
    'get',
    'del',
    'ttl',
    'exist',
    'expire',
    'keys',
    'setex',
    'hget',
    'hset',
    'hkeys',
    'hmset',
    'hdel',
    'hexists',
    'hgetall',
    'hmget',
    'hvals',
    'incr',
    'lpush',
    'rpush',
    'lpop',
    'rpop',
    'lpushx',
    'rpushx',
    'lindex',
    'lindex',
    'lindex',
    'lindex',
    'linsert',
    'lrange',
    'lrem',
    'lset',
    'ltrim',
    'rpoplpush',
    'blpop',
    'brpop',
    'brpoplpush',
    'psubscribe',
    'publish',
    'pubsub',
    'punsubscribe',
    'subscribe',
    'unsubscribe',
    'setbit',
    'getbit',
    'bitop',
    'bitcount',
    'bitfiled',
    'getrange',
    'setrange',
];

/**
 * 缓存管理器
 */
class CacheManager {
    constructor(conf) {
        /**
         port: 6379,          // Redis port
         host: '127.0.0.1',   // Redis host
         family: 4,           // 4 (IPv4) or 6 (IPv6)
         password: 'auth',
         db: 0
         */
        this.conf = conf;
        this.connected = false;
        if (!this.conf.retry_strategy) {
            this.conf.retry_strategy = this.autoRetry;
        }
        /*if (!this.conf.retry_max_delay) {
            this.conf.reconnectOnError = this.reconnectOnError;
        }*/


        this.conn = Redis.createClient(this.conf.port, this.conf.host);
        this.conn.auth(this.conf.password);
        this.conn.on('error', this.onError.bind(this));

        for (let key of keys) {
            this.bindFunc(key);
        }
    }

    async start(db) {
        let self = this;
        return new Promise(function (resolve, reject) {
            self.conn.select(db || self.conf.db, function (err) {
                if (err) {
                    console.error(err);
                    self.connected = false;
                    reject(err);
                }
                else {
                    self.connected = true;
                    console.log(`connected to redis ${self.conf.host}:${self.conf.port}:${self.conf.db}`);
                    resolve();
                }
            });
        });
    }

    stop() {
        if (this.conn) {
            this.conn.end();
            this.conn.quit();
            console.log('redis db connect closed');
        }
    }

    /**
     * 重连机制
     * @param times
     * @returns {number}
     */
    autoRetry(times) {
        return Math.min(times * 50, 2000);
    }

    /**
     * 异常处理机制
     * @param err
     */
    onError(err) {
        console.error(err);
    }

    bindFunc(name) {
        let self = this;
        this[name] = async function () {
            if (!self.conn) {
                console.warn('redis conn is null');
                return;
            }

            let hasEmptyArg = false;
            let args = [];
            for (let arg of arguments) {
                if (arg === undefined) {
                    hasEmptyArg = true;
                }
                if (typeof(arg) === 'object') {
                    args.push(JSON.stringify(arg));
                }
                else {
                    args.push(arg);
                }
            }
            if (this === 'hmget' && hasEmptyArg) {
                console.warn('hmget', args);
            }

            return new Promise(function (resolve, reject) {
                args.push(function (err, res) {
                    if (err) {
                        console.error(err);
                        reject(err);
                    }
                    else {
                        if (!res) {
                            resolve(res);
                            return;
                        }
                        else if (res && res.length === 1 && !res[0]) {
                            resolve(null);
                            return;
                        }
                        try {
                            resolve(JSON.parse(res));
                        }
                        catch (ex) {
                            resolve(res);
                        }
                    }
                });
                self.conn[name].apply(self.conn, args);
            });
        }.bind(name);
    }

    /**
     * 事务
     * @returns {Promise}
     */
    async multi() {
        if (!this.conn) {
            console.warn('redis conn is null');
            return;
        }
        let self = this;
        let args = arguments;
        return new Promise(function (resolve, reject) {
            let multi = self.conn.multi(args);
            multi.exec(function (err, res) {
                if (err) {
                    console.error(err);
                    reject(err);
                }
                else {
                    resolve(res);
                }
            });
        });

    }
}


module.exports = CacheManager;